package com.jiangyg.mall.authz.service.impl;

import com.jiangyg.mall.admin.vo.UserAuthzInfoVO;
import com.jiangyg.mall.admin.service.ResourceService;
import com.jiangyg.mall.admin.service.UserService;
import com.jiangyg.mall.authz.constant.AuthzConstant;
import com.jiangyg.mall.authz.constant.SecurityConstant;
import com.jiangyg.mall.authz.UserInfo;
import com.jiangyg.mall.authz.service.AdminAuthenticationService;
import com.jiangyg.mall.authz.support.authentication.admin.AdminUserDetails;
import com.jiangyg.mall.authz.support.authentication.exception.UnknowException;
import com.jiangyg.mall.core.enums.Enable;
import com.jiangyg.mall.core.support.cache.CacheAdapter;
import com.jiangyg.mall.core.utils.Assert;
import com.jiangyg.mall.core.utils.Logger;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ObjectUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 类描述：后台管理用户信息查询实现
 *
 * @author jiangyg
 * @version 4.0
 * @date 2022-01-05
 */
@Slf4j
@Service("adminAuthenticationService")
public class AdminAuthenticationServiceImpl implements AdminAuthenticationService {

    /**
     * 缓存适配器
     */
    private final CacheAdapter cacheAdapter;

    @Reference(protocol = "dubbo")
    private UserService userService;

    @Reference
    private ResourceService resourceService;

    @Autowired
    public AdminAuthenticationServiceImpl(CacheAdapter cacheAdapter) {
        this.cacheAdapter = cacheAdapter;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        try {
            // TODO 追加 Dubbo 权限校验
            UserAuthzInfoVO user = userService.selectAuthzInfoByAccount(username);
            if (user == null) {
                throw new UsernameNotFoundException("用户名不存在！");
            }
            return new AdminUserDetails(user);
        } catch (AuthenticationException ex) {
            throw ex;
        } catch (Exception ex) {
            // TODO 待测试下 skywalking 不跑异常只输出error异常是否能采集
            final String errMsg = String.format("后台管理用户[%s]登录出现未知异常，", username);
            Logger.error(log, () -> errMsg, ex);
            throw new UnknowException(errMsg, ex);
        }
    }

    @Override
    public void loadGrantedAuthorities(String userId) {
        // 1. 根据userId查询权限列表
        final List<String> authorities = resourceService.selectGrantedAuthorities(Long.parseLong(userId));
        // 2. 把权限信息放入缓存
        final long timeout = SecurityConstant.timeout();
        this.cacheAdapter.sput(AuthzConstant.authoritiesCacheKey(userId), authorities, timeout);
    }

    @Override
    public boolean hasAuthorities(Authentication authentication) {
        // 获取待校验请求地址
        final String authRequestPath = ObjectUtils.toString(authentication.getCredentials());
        // 获取用户信息
        final UserInfo user = (UserInfo) authentication.getDetails();
        // 开始校验
        final String authoritiesCacheKey = AuthzConstant.authoritiesCacheKey(user.getId());
        final boolean b = this.cacheAdapter.scontains(authoritiesCacheKey, authRequestPath);
        Logger.debug(log, () -> String.format("后台管理访问权限校验：用户[%s]资源[%s]鉴权结果为[%b]……", user.getId(), authRequestPath, b));
        return b;
    }

    @Override
    public boolean isLogouted(UserInfo user) {
        Assert.notNull(user);
        final String logoutCacheKey = AuthzConstant.logoutCacheKey(user.getJti());
        return this.cacheAdapter.containsKey(logoutCacheKey);
    }

    @Override
    public void logout(UserInfo user) {
        Assert.notNull(user);
        // 1. 计算令牌过期时间（单位秒），额外追加3秒，防止小概率的误差
        final long timeout = (user.getExp() - System.currentTimeMillis()) / 1000 + 3;
        // 2. 把退出登录标识放入缓存，标识此token失效
        final String jti = user.getJti();
        final String logoutCacheKey = AuthzConstant.logoutCacheKey(jti);
        cacheAdapter.put(logoutCacheKey, Enable.Y.scode, timeout);
    }

}
